/*
    Licensed to the Apache Software Foundation (ASF) under one
    or more contributor license agreements.  See the NOTICE file
    distributed with this work for additional information
    regarding copyright ownership.  The ASF licenses this file
    to you under the Apache License, Version 2.0 (the
    "License"); you may not use this file except in compliance
    with the License.  You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing,
    software distributed under the License is distributed on an
    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    KIND, either express or implied.  See the License for the
    specific language governing permissions and limitations
    under the License.
 */
package org.apache.wiki.auth.acl;

import org.apache.log4j.Logger;
import org.apache.wiki.Wiki;
import org.elwiki_data.Acl;
import org.elwiki_data.AclEntry;
import org.elwiki_data.Elwiki_dataFactory;
import org.elwiki_data.PageAttachment;
import org.elwiki_data.WikiPage;
import org.apache.wiki.api.core.Context;
import org.apache.wiki.api.core.Engine;
import org.apache.wiki.api.exceptions.ProviderException;
import org.apache.wiki.auth.AuthorizationManager;
import org.apache.wiki.auth.WikiSecurityException;
import org.apache.wiki.auth.permissions.PagePermission;
import org.apache.wiki.auth.permissions.PermissionFactory;
import org.apache.wiki.pages0.PageLock;
import org.apache.wiki.pages0.PageManager;
import org.apache.wiki.render0.RenderingManager;
import org.apache.wiki.util.comparators.PrincipalComparator;

import java.security.Permission;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Default implementation that parses Acls from wiki page markup.
 *
 * @since 2.3
 */
public class DefaultAclManager implements AclManager {

    private static final Logger log = Logger.getLogger(DefaultAclManager.class);

    private AuthorizationManager m_auth = null;
    private Engine m_engine = null;

    /** {@inheritDoc} */
    @Override
    public Acl parseAcl( final WikiPage page, final String ruleLine ) throws WikiSecurityException {
        Acl acl = page.getAcl();
        if (acl == null) {
            acl = Wiki.acls().acl();
        }

        try {
            final StringTokenizer fieldToks = new StringTokenizer(ruleLine);
            fieldToks.nextToken();
            final String actions = fieldToks.nextToken();

            while( fieldToks.hasMoreTokens() ) {
                final String principalName = fieldToks.nextToken(",").trim();
                final Principal principal = m_auth.resolvePrincipal(principalName);
                final AclEntry oldEntry = acl.getEntry(principal);

                if( oldEntry != null ) {
                    log.debug( "Adding to old acl list: " + principal + ", " + actions );
                    oldEntry.getPermission().add( PermissionFactory.getPagePermission( page, actions ) );
                } else {
                    log.debug( "Adding new acl entry for " + actions );
                    final AclEntry entry = Wiki.acls().entry();
                    entry.setPrincipal( principal );
                    entry.getPermission().add( PermissionFactory.getPagePermission( page, actions ) );

                    acl.getAclEntries().add( entry );
                }
            }

            page.setAcl( acl );
            log.debug( acl.toString() );
        } catch( final NoSuchElementException nsee ) {
            log.warn( "Invalid access rule: " + ruleLine + " - defaults will be used." );
            throw new WikiSecurityException( "Invalid access rule: " + ruleLine, nsee );
        } catch( final IllegalArgumentException iae ) {
            throw new WikiSecurityException("Invalid permission type: " + ruleLine, iae);
        }

        return acl;
    }


    /** {@inheritDoc} */
    @Override
    public Acl getPermissions( final WikiPage page ) {
        //  Does the page already have cached ACLs?
        Acl acl = page.getAcl();
        log.debug( "page=" + page.getName() + "\n" + acl );

        if( acl == null ) {
            //  If null, try the parent.
            if( page instanceof PageAttachment ) {
            	/* :FVK: этот код не выполнится никогда - так как PageAttachment не наследуется от  WikiPage.
                final WikiPage parent = m_engine.getManager( PageManager.class ).getPage( ( ( PageAttachment ) page ).getParentName() );
                acl = getPermissions(parent);
                */
            } else {
            	/*:FVK:
                //  Or, try parsing the page
                final Context ctx = Wiki.context().create( m_engine, page );
                ctx.setVariable( Context.VAR_EXECUTE_PLUGINS, Boolean.FALSE );
                m_engine.getManager( RenderingManager.class ).getHTML(ctx, page);

                if (page.getAcl() == null) {
                    page.setAcl( Wiki.acls().acl() );
                }
                acl = page.getAcl();
                */
            	acl = Elwiki_dataFactory.eINSTANCE.createAcl();
            }
        }

        return acl;
    }

    /** {@inheritDoc} */
    @Override
    public void setPermissions( final WikiPage page, final Acl acl ) throws WikiSecurityException {
        final PageManager pageManager = m_engine.getManager( PageManager.class );

        // Forcibly expire any page locks
        final PageLock lock = pageManager.getCurrentLock( page );
        if( lock != null ) {
            pageManager.unlockPage( lock );
        }

        // Remove all of the existing ACLs.
        final String pageText = m_engine.getManager( PageManager.class ).getPureText( page );
        final Matcher matcher = DefaultAclManager.ACL_PATTERN.matcher( pageText );
        final String cleansedText = matcher.replaceAll("" );
        final String newText = DefaultAclManager.printAcl( page.getAcl() ) + cleansedText;
        try {
            pageManager.putPageText( page, newText );
        } catch( final ProviderException e ) {
            throw new WikiSecurityException( "Could not set Acl. Reason: ProviderExcpetion " + e.getMessage(), e );
        }
    }

    /**
     * Generates an ACL string for inclusion in a wiki page, based on a supplied Acl object. All of the permissions in this Acl are
     * assumed to apply to the same page scope. The names of the pages are ignored; only the actions and principals matter.
     *
     * @param acl the ACL
     * @return the ACL string
     */
    protected static String printAcl( final Acl acl ) {
        // Extract the ACL entries into a Map with keys == permissions, values == principals
        final Map< String, List< Principal > > permissionPrincipals = new TreeMap<>();
        for (AclEntry entry : acl.getAclEntries()) {
			Principal principal = entry.getPrincipal();
			for (Permission permission : entry.getPermission()) {
				List<Principal> principals = permissionPrincipals.get(permission.getActions());
				if (principals == null) {
					principals = new ArrayList<Principal>();
					String action = permission.getActions();
					if (action.indexOf(',') != -1) {
						throw new IllegalStateException("AclEntry permission cannot have multiple targets.");
					}
					permissionPrincipals.put(action, principals);
				}
				principals.add(principal);
			}        	
        }

        // Now, iterate through each permission in the map and generate an ACL string
        final StringBuilder s = new StringBuilder();
        for( final Map.Entry< String, List< Principal > > entry : permissionPrincipals.entrySet() ) {
            final String action = entry.getKey();
            final List< Principal > principals = entry.getValue();
            principals.sort( new PrincipalComparator() );
            s.append( "[{ALLOW " ).append( action ).append( " " );
            for( int i = 0; i < principals.size(); i++ ) {
                final Principal principal = principals.get( i );
                s.append( principal.getName() );
                if( i < ( principals.size() - 1 ) ) {
                    s.append( "," );
                }
            }
            s.append( "}]\n" );
        }
        return s.toString();
    }

	// -- service support ---------------------------------

	public synchronized void startup() {
		//
	}

	public synchronized void shutdown() {
		//
	}

    /** {@inheritDoc} */
    @Override
    public void initialize( final Engine engine, final Properties props ) {
        m_auth = engine.getManager( AuthorizationManager.class );
        m_engine = engine;
    }
	
}